package transport

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"sync"

	"golang.org/x/oauth2"

	"github.com/stacklok/toolhive/pkg/container"
	"github.com/stacklok/toolhive/pkg/container/docker"
	rt "github.com/stacklok/toolhive/pkg/container/runtime"
	"github.com/stacklok/toolhive/pkg/logger"
	transporterrors "github.com/stacklok/toolhive/pkg/transport/errors"
	"github.com/stacklok/toolhive/pkg/transport/middleware"
	"github.com/stacklok/toolhive/pkg/transport/proxy/transparent"
	"github.com/stacklok/toolhive/pkg/transport/types"
)

const (
	// LocalhostName is the standard hostname for localhost
	LocalhostName = "localhost"
	// LocalhostIPv4 is the standard IPv4 address for localhost
	LocalhostIPv4 = "127.0.0.1"
)

// HTTPTransport implements the Transport interface using Server-Sent/Streamable Events.
type HTTPTransport struct {
	transportType     types.TransportType
	host              string
	proxyPort         int
	targetPort        int
	targetHost        string
	containerName     string
	targetURI         string
	deployer          rt.Deployer
	debug             bool
	middlewares       []types.NamedMiddleware
	prometheusHandler http.Handler
	authInfoHandler   http.Handler

	// Remote MCP server support
	remoteURL string

	// tokenSource is the OAuth token source for remote authentication
	tokenSource oauth2.TokenSource

	// Mutex for protecting shared state
	mutex sync.Mutex

	// Transparent proxy
	proxy types.Proxy

	// Shutdown channel
	shutdownCh chan struct{}

	// Container monitor
	monitor rt.Monitor
	errorCh <-chan error

	// Container exit error (for determining if restart is needed)
	containerExitErr error
	exitErrMutex     sync.Mutex
}

// NewHTTPTransport creates a new HTTP transport.
func NewHTTPTransport(
	transportType types.TransportType,
	host string,
	proxyPort int,
	targetPort int,
	deployer rt.Deployer,
	debug bool,
	targetHost string,
	authInfoHandler http.Handler,
	prometheusHandler http.Handler,
	middlewares ...types.NamedMiddleware,
) *HTTPTransport {
	if host == "" {
		host = LocalhostIPv4
	}

	// If targetHost is not specified, default to localhost
	if targetHost == "" {
		targetHost = LocalhostIPv4
	}

	return &HTTPTransport{
		transportType:     transportType,
		host:              host,
		proxyPort:         proxyPort,
		middlewares:       middlewares,
		targetPort:        targetPort,
		targetHost:        targetHost,
		deployer:          deployer,
		debug:             debug,
		prometheusHandler: prometheusHandler,
		authInfoHandler:   authInfoHandler,
		shutdownCh:        make(chan struct{}),
	}
}

// SetRemoteURL sets the remote URL for the MCP server
func (t *HTTPTransport) SetRemoteURL(remoteURL string) {
	t.remoteURL = remoteURL
}

// SetTokenSource sets the OAuth token source for remote authentication
func (t *HTTPTransport) SetTokenSource(tokenSource oauth2.TokenSource) {
	t.tokenSource = tokenSource
}

// createTokenInjectionMiddleware creates a middleware that injects the OAuth token into requests
func (t *HTTPTransport) createTokenInjectionMiddleware() types.MiddlewareFunction {
	return middleware.CreateTokenInjectionMiddleware(t.tokenSource)
}

// Mode returns the transport mode.
func (t *HTTPTransport) Mode() types.TransportType {
	return t.transportType
}

// ProxyPort returns the proxy port used by the transport.
func (t *HTTPTransport) ProxyPort() int {
	return t.proxyPort
}

// setContainerName configures the transport with the container name.
// This is an unexported method used by the option pattern.
func (t *HTTPTransport) setContainerName(containerName string) {
	t.mutex.Lock()
	defer t.mutex.Unlock()
	t.containerName = containerName
}

// setTargetURI configures the transport with the target URI for proxying.
// This is an unexported method used by the option pattern.
func (t *HTTPTransport) setTargetURI(targetURI string) {
	t.mutex.Lock()
	defer t.mutex.Unlock()
	t.targetURI = targetURI
}

// Start initializes the transport and begins processing messages.
// The transport is responsible for starting the container.
func (t *HTTPTransport) Start(ctx context.Context) error {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	if t.deployer == nil && t.remoteURL == "" {
		return fmt.Errorf("container deployer not set")
	}

	// Determine target URI
	var targetURI string

	if t.remoteURL != "" {
		// For remote MCP servers, construct target URI from remote URL
		remoteURL, err := url.Parse(t.remoteURL)
		if err != nil {
			return fmt.Errorf("failed to parse remote URL: %w", err)
		}
		targetURI = (&url.URL{
			Scheme: remoteURL.Scheme,
			Host:   remoteURL.Host,
		}).String()
		logger.Infof("Setting up transparent proxy to forward from host port %d to remote URL %s",
			t.proxyPort, targetURI)
	} else {
		if t.containerName == "" {
			return transporterrors.ErrContainerNameNotSet
		}

		// For local containers, use the configured target URI
		if t.targetURI == "" {
			return fmt.Errorf("target URI not set for HTTP transport")
		}
		targetURI = t.targetURI
		logger.Infof("Setting up transparent proxy to forward from host port %d to %s",
			t.proxyPort, targetURI)
	}

	// Create middlewares slice
	var middlewares []types.NamedMiddleware

	// Add the transport's existing middlewares
	middlewares = append(middlewares, t.middlewares...)

	// Add OAuth token injection middleware for remote authentication if we have a token source
	if t.remoteURL != "" && t.tokenSource != nil {
		tokenMiddleware := t.createTokenInjectionMiddleware()
		middlewares = append(middlewares, types.NamedMiddleware{
			Name:     "oauth-token-injection",
			Function: tokenMiddleware,
		})
	}

	// Create the transparent proxy
	t.proxy = transparent.NewTransparentProxy(
		t.host,
		t.proxyPort,
		targetURI,
		t.prometheusHandler,
		t.authInfoHandler,
		true,
		t.remoteURL != "",
		string(t.transportType),
		middlewares...)
	if err := t.proxy.Start(ctx); err != nil {
		return err
	}

	logger.Infof("HTTP transport started for %s on port %d", t.containerName, t.proxyPort)

	// For remote MCP servers, we don't need container monitoring
	if t.remoteURL != "" {
		return nil
	}

	// Create a container monitor
	monitorRuntime, err := container.NewFactory().Create(ctx)
	if err != nil {
		return fmt.Errorf("failed to create container monitor: %v", err)
	}
	t.monitor = container.NewMonitor(monitorRuntime, t.containerName)

	// Start monitoring the container
	t.errorCh, err = t.monitor.StartMonitoring(ctx)
	if err != nil {
		return fmt.Errorf("failed to start container monitoring: %v", err)
	}

	// Start a goroutine to handle container exit
	go t.handleContainerExit(ctx)

	return nil
}

// Stop gracefully shuts down the transport and the container.
func (t *HTTPTransport) Stop(ctx context.Context) error {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	// Signal shutdown
	close(t.shutdownCh)

	// For remote MCP servers, we don't need container monitoring
	if t.remoteURL == "" {
		// Stop the monitor if it's running
		if t.monitor != nil {
			t.monitor.StopMonitoring()
			t.monitor = nil
		}

		// Stop the container if deployer is available
		if t.deployer != nil && t.containerName != "" {
			if err := t.deployer.StopWorkload(ctx, t.containerName); err != nil {
				return fmt.Errorf("failed to stop workload: %w", err)
			}
		}
	}

	// Stop the transparent proxy
	if t.proxy != nil {
		if err := t.proxy.Stop(ctx); err != nil {
			logger.Warnf("Warning: Failed to stop proxy: %v", err)
		}
	}

	return nil
}

// handleContainerExit handles container exit events.
func (t *HTTPTransport) handleContainerExit(ctx context.Context) {
	select {
	case <-ctx.Done():
		return
	case err := <-t.errorCh:
		// Store the exit error so runner can check if restart is needed
		t.exitErrMutex.Lock()
		t.containerExitErr = err
		t.exitErrMutex.Unlock()

		logger.Warnf("Container %s exited: %v", t.containerName, err)

		// Check if container was removed (not just exited) using typed error
		if errors.Is(err, docker.ErrContainerRemoved) {
			logger.Infof("Container %s was removed. Stopping proxy and cleaning up.", t.containerName)
		} else {
			logger.Infof("Container %s exited. Will attempt automatic restart.", t.containerName)
		}

		// Stop the transport when the container exits/removed
		if stopErr := t.Stop(ctx); stopErr != nil {
			logger.Errorf("Error stopping transport after container exit: %v", stopErr)
		}
	}
}

// ShouldRestart returns true if the container exited and should be restarted.
// Returns false if the container was removed (intentionally deleted).
func (t *HTTPTransport) ShouldRestart() bool {
	t.exitErrMutex.Lock()
	defer t.exitErrMutex.Unlock()

	if t.containerExitErr == nil {
		return false // No exit error, normal shutdown
	}

	// Don't restart if container was removed (use typed error check)
	return !errors.Is(t.containerExitErr, docker.ErrContainerRemoved)
}

// IsRunning checks if the transport is currently running.
func (t *HTTPTransport) IsRunning(_ context.Context) (bool, error) {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	// Check if the shutdown channel is closed
	select {
	case <-t.shutdownCh:
		return false, nil
	default:
		return true, nil
	}
}
